//
//    node-l3qd - The L3q daemon that runs on each compute node.
//    Copyright (C) 2022  Marcus Pedersén marcus@marcux.org
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//



/// Used by the node daemons
pub mod node {
    use::serde::{Serialize, Deserialize};

    /// The L3q daemon request that should
    /// be sent to L3q node daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub struct L3qdRequest {
        pub cmd: L3qdCommand,
        pub validate: String,
    }
    
    
    /// Represent commands sent from
    /// L3q daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub enum L3qdCommand {
        Status,
        ExecuteL3qdJob { job: NodeL3qdJob },
        CancelL3qdJob { jobid: u64 },
    }
    
    
    /// Represents L3q Node daemon
    /// response to the L3q daemon
    #[derive(Debug, Serialize, Deserialize)]
    pub struct L3qdNodeResponse {
        pub data: L3qdNodeData,
        pub validate: String,
    }

    
    /// Represents the data that
    /// is sent as a response to
    /// the L3q daemon.
    #[derive(Debug, Serialize, Deserialize)]
    pub enum L3qdNodeData {
        NodeStatus {
            total_cpus: usize,
            used_cpus: i32,
            total_memory: u64,
            used_memory: u64,
            total_swap: u64,
            used_swap: u64,
        },
        Error {
            msg: String,
        },
        TaskExecuted {
            jobid: u64,
            task: Vec<u64>,
        },
	CancelledTasks {
            tasks: Vec<TaskStatus>,
	},
        StatusTasks {
            tasks: Vec<TaskStatus>,
            node_status: Box<Self>,
        }
    }


    /// Represent needed info
    /// on a job for node-l3qd
    /// to be able to run it.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct NodeL3qdJob {
        pub jobid: u64,
        pub cgroup_cores_alloc: Option<String>,
        pub cores_alloc: u64,
        pub memory_alloc: u64,
        pub exec_type: NodeL3qdExecType,
        pub tasks: Vec<NodeL3qdTask>,
        pub user: String,
        pub group: String,
    }

    
    /// Node-l3qd representation
    /// of a task.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct NodeL3qdTask {
        pub taskid: u64,
        pub command: String,
        pub working_directory: String,
    }


    /// What way tasks in a job
    /// should execute, one after
    /// each other or at the same
    /// time.
    #[derive(Debug, PartialEq, Serialize, Deserialize)]
    pub enum NodeL3qdExecType {
        Sequence,
        Parallel
    }

    /// Represent info collected
    /// from running task
    #[derive(Debug, Serialize, Deserialize, Clone)]
    pub struct TaskStatus {
        pub jobid: u64,
        pub taskid: u64,
        pub unitname: String,
        pub start_time: String,
        pub end_time: String,
        pub mono_start_time: u64,
        pub mono_end_time: u64,
        pub active_state: String,
        pub sub_state: String,
        pub exit_code: u16,
        pub main_pid: u32,
        pub memory_peak: u64,
        pub pids_peak: u32,
        pub stdout: String,
        pub stderr: String,
    }
    
    impl TaskStatus {
        /// Returns a new empty struct
        pub fn new() -> TaskStatus {
            TaskStatus {
                jobid: 0,
                taskid: 0,
                unitname: String::from(""),
                start_time: String::from(""),
                end_time: String::from(""),
                mono_start_time: 0,
                mono_end_time: 0,
                active_state: String::from(""),
                sub_state: String::from(""),
                exit_code: 0,
                main_pid: 0,
                memory_peak: 0,
                pids_peak: 0,
                stdout: String::from(""),
                stderr: String::from(""),
            }
        }
    }
}


/// Used by all
pub mod all {
    use serde::{Serialize, Deserialize};
    use reqwest::blocking::Client;

    
    /// Response struct received from L3q daemon
    /// to node L3q daemon.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct L3qdResponse {
        pub validate: String,
        pub result: Result<String, String>,
    }

    
    /// Request struct sent to L3q daemon
    /// for validation with daemon.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct L3qValidateRequest {
        pub password: String,
        pub validate: Option<String>,
        pub hostname: String,
        pub port: u16,
        pub action: String,
    }


    /// Validation response from
    /// L3q daemon
    /// Returns the validation string
    /// or the error message in clear text.
    #[derive(Debug, Serialize, Deserialize)]
    pub struct L3qValidateResponse {
        pub validate: Result<String, String>
    }


    /// Initial help text for validation
    /// Checks that effectiv user is root,
    /// otherwise error is returned.
    pub fn validate_print_help() -> Result<(), String> {
        if users::get_effective_uid() != 0 {
            return Err("Permission denied.".to_string());
        }
        
        println!("Please make sure that you have started the validation daemon");
        println!("on the same server as the L3q daemon runs.");
        println!("Start validation daemon with command:");
        println!("l3qd --validate-host");
        println!("");

        Ok(())
    }

    
    /// Function sets up validation
    /// between L3q daemon and
    /// this host.
    /// Used by both l3q and node-l3qd
    /// validate agrument is needed for
    /// node-l3qd, l3q should specify None.
    /// Returned string is validate string
    /// retrieved from L3q daemon
    pub fn validate_host(port: u16, l3qd_host: String, validate: Option<String>, hostname: String, local_daemon_port: u16) -> Result<String, String> {
        let url = format!("https://{}:{}/validate", l3qd_host, port);
    
        // get password 
        let password = rpassword::prompt_password("Enter password, same as entered on L3q validation daemon: ").unwrap();
    
    
        let valr = L3qValidateRequest {
            password: password,
            validate: validate,
            hostname: hostname,
            port: local_daemon_port,
            action: String::from("VALIDATE_KEY"),
        };
        
        let mut cbuilder = Client::builder();
        cbuilder = cbuilder.danger_accept_invalid_certs(true);
        let client = cbuilder.build().unwrap();
        let res;
        match client.post(url).json(&valr).send() {
            Ok(r) => res = r,
            Err(e) => return Err(format!("Failed to post validate to L3q daemon:\n{}", e)),
        }

        match res.json::<L3qValidateResponse>() {
            Ok(v) => v.validate,
            Err(e) => Err(format!("Failed to validate this host!\n{e}")),
        }
    }




    /// Expect arguments to be fetched
    /// from Cargo.toml in each crate.
    /// env!("CARGO_PKG_AUTHORS")
    /// env!("CARGO_PKG_NAME")
    /// env!("CARGO_PKG_VERSION")
    pub fn print_version(cargo_auths: &str, cargo_name: &str, cargo_version: &str) {
    	let auths = cargo_auths.split(":");
    	let mut authors: Vec<&str> = Vec::new();

    	for a in auths {
    	    if a.len() == 0 {
	        continue;
	    }

	    let name: Vec<&str> = a.split("<").collect();

	    if name.len() >= 1 {
	        authors.push(name[0].trim());
	    }
    	}


        println!(" _    ____ ___");
        println!("| |  |__ // _ \\");
        println!("| |__ |_ \\ (_) |");
        println!("|____|___/\\__\\_\\");
    	println!("");

    	println!("{} {}", cargo_name, cargo_version);

	if authors.len() >= 1 {
            println!("Copyright (C) 2023-2024  {}", authors[0]);
    	} 
    	else {
    	     println!("Copyright (C) 2023-2024");
    	}
    	println!("License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.");
    	println!("This is free software: you are free to change and redistribute it.");
    	println!("There is NO WARRANTY, to the extent permitted by law.");
    	println!("");

    	if authors.len() == 1 {
            println!("Written by {}.", authors[0]);
    	}
    	else if authors.len() > 1 {
             print!("Written by ");
             for (i, a) in authors.iter().enumerate() {
	     	 if i == 0 {
                     print!("{}", a);
            	 }
	    	 else if i >= authors.len()-1 {
	    	      print!(" and {}", a);
	    	  }
	    	  else {
	    	       print!(", {}", a);
	    	  }
	      }

	      print!(".\n");
    	 }
    }

}
